from machine import Pin, ADC, Timer
from micropython import schedule


class  KeyPadException(Exception):
	pass


class KeyPad(object):
	"""
	10 按键键盘 ADC 驱动
	使用相同阻值电阻串联方案，只有一个按键的优先级最高，因此不支持多键检测

	作为 HID 输入设备需要实现：
		1. 按键按下：触发 key_down
		2. 按键按下并保持：多次触发 key_down
		3. 按键松开：触发 key_up
	"""
	class Const(object):
		ADC_BIT_WIDTH = 11
		KEY_MAX_VALUE = 2 ** ADC_BIT_WIDTH - 1 # 按键最大值
		KEY_NO_KEY = 55 # 未按键键值范围 0~55
		# KEY_TABLE = [954, KEY_MAX_VALUE] # 所有按键键值

		KEY_INDEX_NONE = 0xff # 无按键索引值

		"""
		key_down_cb 触发后 1000 毫秒，每隔 200 毫秒再触发 1 次 key_down_cb
		"""
		ADC_SCAN_PERIOD = 50 # ADC 扫描间隔
		KEY_HOLD_MS = 1000 # 按键长按触发时间
		KEY_HOLD_TRIGGER_MS = 200 # 按键长按触发后连续触发时间间隔
		KEY_HOLD_COUNT = int((KEY_HOLD_MS + ADC_SCAN_PERIOD - 1) / ADC_SCAN_PERIOD) # 根据 ADC 扫描间隔计算次数
		KEY_HOLD_TRIGGER_COUNT = int((KEY_HOLD_TRIGGER_MS + ADC_SCAN_PERIOD -1) / ADC_SCAN_PERIOD) # 根据 ADC 扫描间隔计算次数

		PERECISION = 0.05 # 电阻精度：1%
		PERECISION_MAX = 1.00 + PERECISION # 电阻精度上限值
		PERECISION_MIN = 1.00 - PERECISION # 电阻精度下限值

	def __init__(self, pin=None, key_table=None, key_down_cb=None, key_hold_cb=None, key_up_cb=None):
		assert pin is not None and pin in range(32, 39), KeyPadException("pin must be in range 32~39")
		assert key_table is not None, KeyPadException("key_table must be specified")

		self.__adc = ADC(Pin(pin, Pin.IN))
		self.__key_table = key_table
		self.__key_count = len(self.__key_table)
		self.__timer = Timer(10)
		self.__key_down_cb = key_down_cb
		self.__key_hold_cb = key_hold_cb
		self.__key_up_cb = key_up_cb
		self.__current_key_index = KeyPad.Const.KEY_INDEX_NONE
		self.__key_hold_counter = 0
		self.__key_hold_trigger_counter = 0

		self.__adc.atten(ADC.ATTN_11DB)
		self.__adc.width(ADC.WIDTH_11BIT)

	def get_key_count(self):
		"""
		获取按键数量
		"""
		return self.__key_count

	def get_adc_width(self):
		"""
		获取 ADC 位宽
		"""
		return KeyPad.Const.ADC_BIT_WIDTH

	def capture(self, for_test=False):
		"""
		开启按键捕捉扫描

		for_test=True 则只打印 ADC 读取的数值
		"""
		try:
			self.__timer.init(
				mode=Timer.PERIODIC,
				period=KeyPad.Const.ADC_SCAN_PERIOD,
				callback=lambda timer: schedule(self.__key_scan, for_test)
			)
		except RuntimeError as re:
			if str(re) in ("schedule queue full"): pass

	def __get_key_index(self, value):
		"""
		根据 ADC 数值获取当前按键索引值
		"""
		key_index = KeyPad.Const.KEY_INDEX_NONE

		if self.__key_count == 1:
			key_index = 0
		elif value == KeyPad.Const.KEY_MAX_VALUE:
			key_index = self.__key_count - 1
		else:
			for index in range(self.__key_count):
				if value > self.__key_table[index] * KeyPad.Const.PERECISION_MIN and value < self.__key_table[index] * KeyPad.Const.PERECISION_MAX:
					key_index = index
					break

		return key_index

	def __key_scan(self, for_test):
		value = self.__adc.read()

		if value <= KeyPad.Const.KEY_NO_KEY:
			if self.__current_key_index != KeyPad.Const.KEY_INDEX_NONE:
				if self.__key_up_cb is not None:
					self.__key_up_cb(self.__current_key_index)

					self.__current_key_index = KeyPad.Const.KEY_INDEX_NONE
					self.__key_hold_counter = 0
					self.__key_hold_trigger_counter = 0

			return

		if for_test:
			print("value:", value)
			return
		
		key_index = self.__get_key_index(value)

		if key_index == KeyPad.Const.KEY_INDEX_NONE: return

		if key_index == self.__current_key_index:
			self.__key_hold_counter += 1

			if self.__key_hold_counter == KeyPad.Const.KEY_HOLD_COUNT:
				self.__key_hold_counter -= 1
				self.__key_hold_trigger_counter += 1

				if self.__key_hold_trigger_counter == KeyPad.Const.KEY_HOLD_TRIGGER_COUNT:
					self.__key_hold_trigger_counter = 0

					if self.__key_hold_cb is not None:
						self.__key_hold_cb(self.__current_key_index)
		else:
			self.__current_key_index = key_index

			if self.__key_down_cb is not None:
				self.__key_down_cb(self.__current_key_index)


def main():
	def key_hold_cb(index):
		print("key {} holding".format(index))

	def key_down_cb(index):
		print("key {} down".format(index))

	def key_up_cb(index):
		print("key {} up".format(index))

	KEY_TABLE = [1280, KeyPad.Const.KEY_MAX_VALUE] # 所有按键键值

	keypad = KeyPad(34, key_table=KEY_TABLE, key_down_cb=key_down_cb, key_hold_cb=key_hold_cb, key_up_cb=key_up_cb)
	keypad.capture()

	print("Keypad key count:", keypad.get_key_count())


if __name__ == "__main__":
	try:
		main()
	except KeyboardInterrupt:
		print("\nPRESS CTRL+D TO RESET DEVICE")
